很多情况下,我们可能需要在获取或更新缓存时执行一些额外操作,Workbox 的插件通过一系列生命周期函数(比如:cacheWillUpdate)为我们提供了可在请求的生命周期内,以操作请求、响应的方式来控制并添加额外行为的机制,本章我们将对其进行详细介绍。

# 内置插件

Workbox 内置了一些插件以满足我们的日常开发需求,它们分别是:

  • workbox.backgroundSync.Plugin:用于后台同步;如果网络请求失败,该请求将会被添加到后台同步队列中,并在触发下一个同步事件时重试该请求。
  • workbox.broadcastUpdate.Plugin:用于广播通知;如果缓存被更新,将在 Broadcast Channel 或通过调用 postMessage 来发送通知给订阅者。
  • workbox.cacheableResponse.Plugin:用于判断请求是否可以被缓存;通过该插件,可以只缓存满足指定条件的请求响应。
  • workbox.expiration.Plugin:用于控制缓存的有效期。
  • workbox.rangeRequests.Plugin:通过该插件,可要求 Service Worker 只返回响应的部分内容。 我们已经在前面的章节中对 workbox.backgroundSync.Pluginworkbox.broadcastUpdate.Pluginworkbox.cacheableResponse.Pluginworkbox.expiration.Plugin 进行了介绍,此处不再重述,本节的剩余部分,我们将对 workbox.rangeRequests.Plugin 的使用进行讨论。

# Range 请求

播放音视频时,我们往往需要边播边下载以减少用户等待时间,此时便可在请求中添加 Range 头来告知服务器只返回资源的指定部分,其格式如下:

Range: <unit>=<range-start>-<range-end>
  • <unit>:范围单位(通常为 bytes)。
  • <range-start>:指定单位下,范围的起始值(类型为整数)。
  • <range-end>:指定单位下,范围的结束值(类型为整数),如不指定此值,则表示范围的结束值为资源的末尾。

如果服务器返回的是 Range 响应,状态码为 206(Partial Content);如请求不合法,服务器返回的状态码为 416(Range Not Satisfiable);如果服务器忽略 Range 头,返回整个资源,其状态码为 200。

# workbox.rangeRequests.Plugin

当我们使用缓存来响应包含 Range 头的请求时,Cache API 将忽略 Range 设置并返回完整的资源,基于此,我们往往使用 workbox.rangeRequests.Plugin 来返回资源指定范围的内容。比如:

workbox.routing.registerRoute(
  /\.mp4$/,
  new workbox.strategies.CacheFirst({
    plugins: [
      new workbox.rangeRequests.Plugin()
    ]
  });
);

同其他内置插件一样,我们可以使用 workbox.rangeRequests.createPartialResponse 在自定义的请求策略中处理 Range 请求,比如:

const response = await workbox.rangeRequests.createPartialResponse(request, cachedResponse);

需要注意的是,参数 request 必须包含 Range 头,否则将抛出 No Range header was found in the Request provided. 异常。

# 自定义插件

我们可以通过创建包含以下方法的对象来构建自定义插件:

  • cacheWillUpdate:方法签名为:({request, response, event}) => Promise<Response|null>,该方法在请求响应将要被缓存时触发,将用此方法的返回值来更新相关请求的缓存(当返回值为 Promise<null> 时,则表明不更新相关缓存)。
  • cacheDidUpdate:方法签名为:({cacheName, request, oldResponse, newResponse, event}) => Promise<Void>,该方法在请求响应已经被缓存后触发,一般在该方法中做一些通知或清理过期缓存之类的工作。
  • cacheKeyWillBeUsed:方法签名为:({request, mode}) => Promise<Request|string>,该方法在获取(mode 为 read)或更新(mode 为 write)缓存时触发,将用此方法的返回值作为缓存的键值来执行缓存操作。
  • cachedResponseWillBeUsed:方法签名为:({cacheName, request, matchOptions, cachedResponse, event}) => Promise<Response | null>,该方法在将缓存结果作为请求响应时触发,将用此方法的返回值作为请求的响应。
  • requestWillFetch: 方法签名为:({request}) => Promise<Request>,该方法在 fetch函数被调用时触发,可返回一个不同的 Request 对象以达到修改请求参数的目的。
  • fetchDidFail:方法签名为:({originalRequest, request, error, event}) => Promise<Void>,该方法仅在 fetch 函数抛出异常时触发,即使服务器返回错误响应,只要 fetch 调用无异常抛出,该方法将不会触发。其中 originalRequestfetch 函数的请求参数,request 为 requestWillFetch 方法的返回值。
  • fetchDidSucceed:方法签名为:({request, response}) => Promise<Void>,该方法在 fetch 函数被成功调用后触发,即使服务器返回错误响应,只要 fetch 调用无异常抛出,该方法依旧会触发。

基于上述方法,我们便可在 Workbox 内置的请求策略中定义自己的插件,比如:

const myPlugin = {
  cacheWillUpdate: async ({ request, response, event }) => {
    return response;
  },
  // ... 其他方法
};

workbox.routing.registerRoute(
  /\.mp4$/,
  new workbox.strategies.CacheFirst({
    plugins: [
      myPlugin
    ]
  });
);

# 总结

本章我们首先介绍了 Workbox 的内置插件,然后对 workbox.rangeRequests.Plugin 的使用进行了详细讨论,最后介绍了如何实现自定义插件。至此,我们完成了 Workbox 在 Service Worker 线程上所有特性的学习,下一章我们将转战 UI 线程,来看一看 Workbox 在页面主线程中为我们带来了哪些惊喜。

阅读全文